/**
 * 
 */
package com.p2p.base.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author ruiqi
 *
 */
public class HttpInclude{
    private final static Log log = LogFactory.getLog(HttpInclude.class);
    
    public static String sessionIdKey = "JSESSIONID";
    
private HttpServletRequest request;
private HttpServletResponse response;

public HttpInclude(HttpServletRequest request, HttpServletResponse response) {
    this.request = request;
    this.response = response;
}
public HttpInclude() {
  
}

    public String include(String includePath) {
            StringWriter sw = new StringWriter(8192);
            include(includePath,sw);
            return sw.toString();
}

    public String include(String includePath,HttpServletRequest request, HttpServletResponse response) {
    	this.request = request;
        this.response = response;
        StringWriter sw = new StringWriter(8192);
        include(includePath,sw);
        return sw.toString();
    }

    public void include(String includePath,Writer writer) {
    try {
        if(isRemoteHttpRequest(includePath)) {
            getRemoteContent(includePath,writer);
        }else {
            getLocalContent(includePath,writer);
        }
    } catch (ServletException e) {
        throw new RuntimeException("include error,path:"+includePath+" cause:"+e,e);
    } catch (IOException e) {
        throw new RuntimeException("include error,path:"+includePath+" cause:"+e,e);
    }
}
    
private static boolean isRemoteHttpRequest(String includePath) {
    return  includePath != null && (
                            includePath.toLowerCase().startsWith("http://") ||
                            includePath.toLowerCase().startsWith("https://")
                    );
}

private void getLocalContent(String includePath,Writer writer) throws ServletException, IOException {
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream(8192);
    
    CustomOutputHttpServletResponseWrapper customResponse = new CustomOutputHttpServletResponseWrapper(response,writer,outputStream);
    request.getRequestDispatcher(includePath).include(request, customResponse);
    
    customResponse.flushBuffer();
    if(customResponse.useOutputStream) {
            writer.write(outputStream.toString(response.getCharacterEncoding())); //TODO: response.getCharacterEncoding()有可能为null
    }
    writer.flush();
}

//TODO handle cookies and http query parameters encoding
//TODO set inheritParams from request
private void getRemoteContent(String urlString,Writer writer) throws MalformedURLException, IOException {
    URL url = new URL(getWithSessionIdUrl(urlString));
            URLConnection conn = url.openConnection();
    setConnectionHeaders(urlString, conn);
    InputStream input = conn.getInputStream();
    try {
            Reader reader = new InputStreamReader(input,Utils.getContentEncoding(conn,response));
            Utils.copy(reader,writer);
    }finally {
            if(input != null) input.close();
    }
    writer.flush();
}

    private void setConnectionHeaders(String urlString, URLConnection conn) {
            conn.setReadTimeout(6000);
    conn.setConnectTimeout(6000);
    String cookie = getCookieString();
            conn.setRequestProperty("Cookie", cookie);
            //TODO: 用于支持 httpinclude_header.properties
//          conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3");
//          conn.setRequestProperty("Host", url.getHost());
            if(log.isDebugEnabled()) {
                    log.debug("request properties:"+conn.getRequestProperties()+" for url:"+urlString);
            }
    }
    
    //TODO add session id with url
    private String getWithSessionIdUrl(String url) {
            return url;
    }

private static final String SET_COOKIE_SEPARATOR="; ";
    private String getCookieString() {
            StringBuffer sb = new StringBuffer(64);
            Cookie[] cookies = request.getCookies();
            if(cookies != null ) {
                    for(Cookie c : cookies) {
                            if(!sessionIdKey.equals(c.getName())) {
                                    sb.append(c.getName()).append("=").append(c.getValue()).append(SET_COOKIE_SEPARATOR);
                            }
                    }
            }
            
            String sessionId = Utils.getSessionId(request);
            if(sessionId != null) {
                    sb.append(sessionIdKey).append("=").append(sessionId).append(SET_COOKIE_SEPARATOR);
            }
            return sb.toString();
    }
    
    public static class CustomOutputHttpServletResponseWrapper extends HttpServletResponseWrapper {
    public boolean useWriter = false;
    public boolean useOutputStream = false;
//    
    private PrintWriter printWriter;
    private ServletOutputStream servletOutputStream;
    
    public CustomOutputHttpServletResponseWrapper(HttpServletResponse response,final Writer customWriter,final OutputStream customOutputStream) {
        super(response);
        this.printWriter = new PrintWriter(customWriter);
        this.servletOutputStream = new ServletOutputStream() {
            @Override
            public void write(int b) throws IOException {
                customOutputStream.write(b);
            }
            @Override
            public void write(byte[] b) throws IOException {
                customOutputStream.write(b);
            }
            @Override
            public void write(byte[] b, int off, int len)throws IOException {
                customOutputStream.write(b, off, len);
            }
        };
    }
    @Override
    public PrintWriter getWriter() throws IOException {
        if(useOutputStream) throw new IllegalStateException("getOutputStream() has already been called for this response");
        useWriter = true;
        return printWriter;
    }
    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        if(useWriter) throw new IllegalStateException("getWriter() has already been called for this response");
        useOutputStream = true;
        return servletOutputStream;
    }
    
    @Override
    public void flushBuffer() throws IOException {
        if(useWriter) printWriter.flush();
        if(useOutputStream) servletOutputStream.flush();
    }
    
    }

static class Utils { 
    static String getContentEncoding(URLConnection conn,HttpServletResponse response) {
            String contentEncoding = conn.getContentEncoding();
        if(conn.getContentEncoding() == null) {
            contentEncoding = parseContentTypeForCharset(conn.getContentType());
            if(contentEncoding == null) {
                    contentEncoding =  response.getCharacterEncoding();
            }
        } else {
            contentEncoding = conn.getContentEncoding();
        }
            return contentEncoding;
    }
    static Pattern p = Pattern.compile("(charset=)(.*)",Pattern.CASE_INSENSITIVE);
    private static String parseContentTypeForCharset(String contentType) {
            if(contentType == null) return null;
            Matcher m = p.matcher(contentType);
            if(m.find()) {
                    return m.group(2).trim();
            }
                    return null;
            }

            private static void copy(Reader in, Writer out) throws IOException {
        char[] buff = new char[8192];
        while(in.read(buff) >= 0) {
            out.write(buff);
        }
    }
    
    private static String getSessionId(HttpServletRequest request) {
            HttpSession session = request.getSession(false);
            if(session == null) {
                    return null;
            }
            return session.getId();
    }
}

/*@Override
public void execute(Environment env, Map params, TemplateModel[] loopVars,
		TemplateDirectiveBody body) throws TemplateException, IOException {
	
	
	env.getOut().write(str);
	
	
}*/
}